# Imports

import cv2
import mediapipe as mp
import pyautogui
import math
from enum import IntEnum
from google.protobuf.json_format import MessageToDict

pyautogui.FAILSAFE = False
mp_drawing = mp.solutions.drawing_utils
mp_hands = mp.solutions.hands


# Gesture Encodings
class Gest(IntEnum):
    # Binary Encoded 有很多没有本阶段用到
    FIST = 0
    PINKY = 1
    RING = 2
    CLICK = 4
    LAST3 = 7
    INDEX = 8
    FIRST2 = 12
    LAST4 = 15
    THUMB = 16
    PALM = 31

    # Extra Mappings
    """
        V_GEST：将鼠标光标移动到指定位置。
        FIST：按下鼠标左键，并持续移动鼠标。
        CLICK：单击鼠标左键。
        INDEX：单击鼠标右键。
        TWO_FINGER_CLOSED：双击鼠标左键。
    """
    V_GEST = 33
    TWO_FINGER_CLOSED = 34
    PINCH_MAJOR = 35
    PINCH_MINOR = 36


# Multi-handedness Labels
class HLabel(IntEnum):
    MINOR = 0
    MAJOR = 1


# Convert Mediapipe Landmarks to recognizable Gestures
# 将Mediapipe Landmarks数据生成手势，设置在current_gesture
class HandRecog:
    """
    Convert Mediapipe Landmarks to recognizable Gestures.
    """

    def __init__(self, hand_label):
        """

        """
        self.finger = 0
        self.ori_gesture = Gest.PALM
        self.prev_gesture = Gest.PALM
        self.frame_count = 0
        self.hand_result = None
        self.hand_label = hand_label

    def update_hand_result(self, hand_result):
        self.hand_result = hand_result
    # 数学函数可不看
    def get_signed_dist(self, point):
        """
        get_signed_dist(point): 返回点之间的有符号欧氏距离。
        """
        sign = -1
        if self.hand_result.landmark[point[0]].y < self.hand_result.landmark[point[1]].y:
            sign = 1
        dist = (self.hand_result.landmark[point[0]].x - self.hand_result.landmark[point[1]].x) ** 2
        dist += (self.hand_result.landmark[point[0]].y - self.hand_result.landmark[point[1]].y) ** 2
        dist = math.sqrt(dist)
        return dist * sign

    # 数学函数可不看
    def get_dist(self, point):
        """
        get_dist(point): 返回点之间的欧氏距离。
        """
        dist = (self.hand_result.landmark[point[0]].x - self.hand_result.landmark[point[1]].x) ** 2
        dist += (self.hand_result.landmark[point[0]].y - self.hand_result.landmark[point[1]].y) ** 2
        dist = math.sqrt(dist)
        return dist

    # 数学函数可不看
    def get_dz(self, point):
        """
        get_dz(point): 返回点在z轴上的绝对差异。
        """
        return abs(self.hand_result.landmark[point[0]].z - self.hand_result.landmark[point[1]].z)

    # Finger_state是手指打开与否 1 if finger is open, else 0
    def set_finger_state(self):
        """
        set_finger_state(): 通过计算指尖、中指节和基节之间的距离比例，设置finger属性的状态。
        -------
        无返回值
        """
        if self.hand_result == None:
            return

        points = [[8, 5, 0], [12, 9, 0], [16, 13, 0], [20, 17, 0]]
        self.finger = 0
        self.finger = self.finger | 0  # thumb
        for idx, point in enumerate(points):

            dist = self.get_signed_dist(point[:2])
            dist2 = self.get_signed_dist(point[1:])

            try:
                ratio = round(dist / dist2, 1)
            except:
                ratio = round(dist2 / 0.01, 1)

            self.finger = self.finger << 1
            if ratio > 0.5:
                self.finger = self.finger | 1
            # 7.5测试
            #print(self.finger)


    # Handling Fluctations due to noise
    def get_gesture(self):
        """
        get_gesture(): 返回表示手势的枚举值（Gest）。处理噪声引起的波动，并处理手势的状态更新。
        -------
        返回值：int类型
        """
        if self.hand_result == None:
            return Gest.PALM

        current_gesture = Gest.PALM
        if self.finger in [Gest.LAST3, Gest.LAST4] and self.get_dist([8, 4]) < 0.05:
            if self.hand_label == HLabel.MINOR:
                current_gesture = Gest.PINCH_MINOR
            else:
                current_gesture = Gest.PINCH_MAJOR

        elif Gest.FIRST2 == self.finger:
            point = [[8, 12], [5, 9]]
            dist1 = self.get_dist(point[0])
            dist2 = self.get_dist(point[1])
            ratio = dist1 / dist2
            # 7.5测试
            print(ratio)
            if ratio > 2.1:
                current_gesture = Gest.V_GEST
            else:
                if self.get_dz([8, 12]) < 1.0:
                    current_gesture = Gest.CLICK
                else:
                    current_gesture = Gest.TWO_FINGER_CLOSED

        else:
            current_gesture = self.finger

        if current_gesture == self.prev_gesture:
            self.frame_count += 1
        else:
            self.frame_count = 0

        self.prev_gesture = current_gesture

        if self.frame_count > 4:
            self.ori_gesture = current_gesture
        return self.ori_gesture


# Executes commands according to detected gestures
class Controller:
    """
   方法：
    getpinchylv(hand_result): 返回当前手部位置y坐标与PINCH手势开始时手部位置y坐标之间的距离。
    getpinchxlv(hand_result): 返回当前手部位置x坐标与PINCH手势开始时手部位置x坐标之间的距离。

    scrollVertical(): 垂直滚动屏幕。
    scrollHorizontal(): 水平滚动屏幕。
    get_position(hand_result): 返回当前手部位置的坐标，并通过阻尼来稳定光标的运动。
    pinch_control_init(hand_result): 初始化PINCH手势控制的属性。
    pinch_control(hand_result, controlHorizontal, controlVertical): 根据PINCH手势的方向调用controlHorizontal或controlVertical方法，并设置pinchlv的值。
    handle_controls(gesture, hand_result): 实现所有手势功能。
    在handle_controls方法中，根据检测到的手势（gesture），执行相应的功能。
    """

    tx_old = 0
    ty_old = 0
    trial = True
    flag = False
    grabflag = False
    # pinchmajorflag = False
    # pinchminorflag = False
    # pinchstartxcoord = None
    # pinchstartycoord = None
    # pinchdirectionflag = None
    # prevpinchlv = 0
    # pinchlv = 0
    framecount = 0
    prev_hand = None
    pinch_threshold = 0.3

    # def getpinchylv(hand_result):
    #     """returns distance beween starting pinch y coord and current hand position y coord."""
    #     dist = round((Controller.pinchstartycoord - hand_result.landmark[8].y) * 10, 1)
    #     return dist
    #
    # def getpinchxlv(hand_result):
    #     """returns distance beween starting pinch x coord and current hand position x coord."""
    #     dist = round((hand_result.landmark[8].x - Controller.pinchstartxcoord) * 10, 1)
    #     return dist

    # Locate Hand to get Cursor Position
    # Stabilize cursor by Dampening
    def get_position(hand_result):
        """
        returns coordinates of current hand position.

        Locates hand to get cursor position also stabilize cursor by
        dampening jerky motion of hand.

        Returns
        -------
        tuple(float, float)
        """
        point = 9
        position = [hand_result.landmark[point].x, hand_result.landmark[point].y]
        sx, sy = pyautogui.size()
        x_old, y_old = pyautogui.position()
        x = int(position[0] * sx)
        y = int(position[1] * sy)
        if Controller.prev_hand is None:
            Controller.prev_hand = x, y
        delta_x = x - Controller.prev_hand[0]
        delta_y = y - Controller.prev_hand[1]

        distsq = delta_x ** 2 + delta_y ** 2
        ratio = 1
        Controller.prev_hand = [x, y]

        if distsq <= 25:
            ratio = 0
        elif distsq <= 900:
            ratio = 0.07 * (distsq ** (1 / 2))
        else:
            ratio = 2.1
        x, y = x_old + delta_x * ratio, y_old + delta_y * ratio
        return (x, y)

    # def pinch_control_init(hand_result):
    #     """Initializes attributes for pinch gesture."""
    #     Controller.pinchstartxcoord = hand_result.landmark[8].x
    #     Controller.pinchstartycoord = hand_result.landmark[8].y
    #     Controller.pinchlv = 0
    #     Controller.prevpinchlv = 0
    #     Controller.framecount = 0

    # Hold final position for 5 frames to change status
    def pinch_control(hand_result, controlHorizontal, controlVertical):
        """
        calls 'controlHorizontal' or 'controlVertical' based on pinch flags,
        'framecount' and sets 'pinchlv'.

        Parameters
        ----------
        hand_result : Object
            Landmarks obtained from mediapipe.
        controlHorizontal : callback function assosiated with horizontal
            pinch gesture.
        controlVertical : callback function assosiated with vertical
            pinch gesture.

        Returns
        -------
        None
        """
        if Controller.framecount == 5:
            Controller.framecount = 0
            Controller.pinchlv = Controller.prevpinchlv

            if Controller.pinchdirectionflag == True:
                controlHorizontal()  # x

            elif Controller.pinchdirectionflag == False:
                controlVertical()  # y

        lvx = Controller.getpinchxlv(hand_result)
        lvy = Controller.getpinchylv(hand_result)

        if abs(lvy) > abs(lvx) and abs(lvy) > Controller.pinch_threshold:
            Controller.pinchdirectionflag = False
            if abs(Controller.prevpinchlv - lvy) < Controller.pinch_threshold:
                Controller.framecount += 1
            else:
                Controller.prevpinchlv = lvy
                Controller.framecount = 0

        elif abs(lvx) > Controller.pinch_threshold:
            Controller.pinchdirectionflag = True
            if abs(Controller.prevpinchlv - lvx) < Controller.pinch_threshold:
                Controller.framecount += 1
            else:
                Controller.prevpinchlv = lvx
                Controller.framecount = 0

    def handle_controls(gesture, hand_result):
        """
        V_GEST：将鼠标光标移动到指定位置。
        FIST：按下鼠标左键，并持续移动鼠标。
        CLICK：单击鼠标左键。
        INDEX：单击鼠标右键。
        TWO_FINGER_CLOSED：双击鼠标左键。
        PINCH_MINOR：通过PINCH手势滚动屏幕。
        PINCH_MAJOR：通过PINCH手势调整系统亮度或音量。
        这些方法通过操作鼠标和滚动功能实现了手势控制。
        """
        x, y = None, None
        if gesture != Gest.PALM:
            x, y = Controller.get_position(hand_result)

        # flag reset
        if gesture != Gest.FIST and Controller.grabflag:
            Controller.grabflag = False
            pyautogui.mouseUp(button="left")

        # if gesture != Gest.PINCH_MAJOR and Controller.pinchmajorflag:
        #     Controller.pinchmajorflag = False
        #
        # if gesture != Gest.PINCH_MINOR and Controller.pinchminorflag:
        #     Controller.pinchminorflag = False

        # implementation
        if gesture == Gest.V_GEST:
            Controller.flag = True
            pyautogui.moveTo(x, y, duration=0.1)

        elif gesture == Gest.FIST:
            # 如果Controller.grabflag抓取第一次
            if not Controller.grabflag:
                Controller.grabflag = True
                pyautogui.mouseDown(button="left")
            pyautogui.moveTo(x, y, duration=0.1)

        elif gesture == Gest.CLICK and Controller.flag:
            pyautogui.click()
            print("aa")
            Controller.flag = False

        elif gesture == Gest.INDEX and Controller.flag:
            pyautogui.click(button='right')
            Controller.flag = False

        elif gesture == Gest.TWO_FINGER_CLOSED and Controller.flag:
            pyautogui.doubleClick()
            Controller.flag = False

        # Gest.PINCH实现
        # elif gesture == Gest.PINCH_MINOR:
        #     if Controller.pinchminorflag == False:
        #         Controller.pinch_control_init(hand_result)
        #         Controller.pinchminorflag = True
        #     Controller.pinch_control(hand_result, Controller.scrollHorizontal, Controller.scrollVertical)
        #
        # elif gesture == Gest.PINCH_MAJOR:
        #     if Controller.pinchmajorflag == False:
        #         Controller.pinch_control_init(hand_result)
        #         Controller.pinchmajorflag = True
        #     Controller.pinch_control(hand_result, Controller.changesystembrightness, Controller.changesystemvolume)


'''
----------------------------------------  Main Class  ----------------------------------------
    Entry point of Gesture Controller
'''


class GestureController:
    """
    程序入口
    """
    gc_mode = 0
    cap = None
    CAM_HEIGHT = None
    CAM_WIDTH = None
    hr_major = None  # Right Hand by default
    hr_minor = None  # Left hand by default
    dom_hand = True

    def __init__(self):
        """Initilaizes attributes."""
        GestureController.gc_mode = 1
        GestureController.cap = cv2.VideoCapture(0)
        GestureController.CAM_HEIGHT = GestureController.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
        GestureController.CAM_WIDTH = GestureController.cap.get(cv2.CAP_PROP_FRAME_WIDTH)

    def classify_hands(results):
        """
        根据从MediaPipe获取的手的分类（左手或右手），设置hr_major和hr_minor的值。根据dom_hand属性确定哪只手是主要手，哪只手是次要手。
        """
        left, right = None, None
        try:
            handedness_dict = MessageToDict(results.multi_handedness[0])
            if handedness_dict['classification'][0]['label'] == 'Right':
                right = results.multi_hand_landmarks[0]
            else:
                left = results.multi_hand_landmarks[0]
        except:
            pass

        try:
            handedness_dict = MessageToDict(results.multi_handedness[1])
            if handedness_dict['classification'][0]['label'] == 'Right':
                right = results.multi_hand_landmarks[1]
            else:
                left = results.multi_hand_landmarks[1]
        except:
            pass

        if GestureController.dom_hand == True:
            GestureController.hr_major = right
            GestureController.hr_minor = left
        else:
            GestureController.hr_major = left
            GestureController.hr_minor = right

    def start(self):
        """
        主程序人口
        """

        handmajor = HandRecog(HLabel.MAJOR)
        handminor = HandRecog(HLabel.MINOR)

        with mp_hands.Hands(max_num_hands=2, min_detection_confidence=0.5, min_tracking_confidence=0.5) as hands:
            while GestureController.cap.isOpened() and GestureController.gc_mode:
                success, image = GestureController.cap.read()

                if not success:
                    print("Ignoring empty camera frame.")
                    continue

                image = cv2.cvtColor(cv2.flip(image, 1), cv2.COLOR_BGR2RGB)
                image.flags.writeable = False
                results = hands.process(image)

                image.flags.writeable = True
                image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)

                if results.multi_hand_landmarks:
                    GestureController.classify_hands(results)
                    # 设置左右手
                    handmajor.update_hand_result(GestureController.hr_major)
                    handminor.update_hand_result(GestureController.hr_minor)
                    # 计算指尖、中指节和基节之间的距离比例，设置finger属性的状态。
                    handmajor.set_finger_state()
                    handminor.set_finger_state()
                    gest_name = handminor.get_gesture()
                    # 根据手势的识别结果调用Controller.handle_controls()方法进行相应的控制操作。
                    if gest_name == Gest.PINCH_MINOR:
                        Controller.handle_controls(gest_name, handminor.hand_result)
                    else:
                        gest_name = handmajor.get_gesture()
                        Controller.handle_controls(gest_name, handmajor.hand_result)

                    for hand_landmarks in results.multi_hand_landmarks:
                        mp_drawing.draw_landmarks(image, hand_landmarks, mp_hands.HAND_CONNECTIONS)
                else:
                    Controller.prev_hand = None
                cv2.imshow('Gesture Controller', image)
                if cv2.waitKey(1) == ord('q'):
                    break
        GestureController.cap.release()
        cv2.destroyAllWindows()


# uncomment to run directly
gc1 = GestureController()
gc1.start()
